package com.snail.compile.processor;

import com.google.auto.service.AutoService;
import com.snail.compile.annotation.Getter;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.snail.compile.annotation.Getter")
@AutoService(Processor.class)
public class GetterProcessor extends AbstractProcessor {
    private Messager messager;

    private TreeMaker treeMaker;

    private JavacTrees trees;

    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.names = Names.instance(context);
        this.treeMaker = TreeMaker.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
        set.forEach(element -> {
            JCTree jcTree = trees.getTree(element);
            jcTree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    List<JCTree.JCVariableDecl> jcVariableDecls = List.nil();
                    List<JCTree.JCMethodDecl> jcMethodDecls = List.nil();
                    for (JCTree jcTree : jcClassDecl.defs) {
                        if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
                            jcVariableDecls = jcVariableDecls.append(jcVariableDecl);
                        } else if (jcTree.getKind().equals(Tree.Kind.METHOD) && ((JCTree.JCMethodDecl) jcTree).getModifiers().flags == Flags.PUBLIC) {
                            jcMethodDecls = jcMethodDecls.prepend((JCTree.JCMethodDecl) jcTree);
                        }
                    }
                    jcVariableDecls.forEach(jcVariableDecl -> {
                        messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterDecl(jcVariableDecl));
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterDecl(jcVariableDecl));
                    });
                    jcMethodDecls.forEach(jcMethodDecl -> {
                        messager.printMessage(Diagnostic.Kind.NOTE, jcMethodDecl.getName() + " has been processed");
                        JCTree.JCFieldAccess println = treeMaker.Select(
                                treeMaker.Select(treeMaker.Ident(names.fromString("System")), names.fromString("out"))
                                , names.fromString("println"));
                        JCTree.JCLiteral test1231 = treeMaker.Literal(TypeTag.CLASS, "test123");
                        JCTree.JCMethodInvocation methodInvoke = treeMaker.Apply(List.nil(), println, List.of(test1231));
                        JCTree.JCExpressionStatement statement = treeMaker.Exec(methodInvoke);
                        jcMethodDecl.body.stats = jcMethodDecl.body.stats.prepend(statement);
                    });
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        return true;
    }

    private JCTree makeSetterDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> listBuffer = new ListBuffer<>();
        JCTree.JCAssign assign = treeMaker.Assign(
                treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),
                treeMaker.Binary(JCTree.Tag.PLUS,
                        treeMaker.Ident(jcVariableDecl.getName()),
                        treeMaker.Literal(TypeTag.CLASS, "test123")
                )
        );
        listBuffer.add(treeMaker.Exec(assign));
        JCTree.JCBlock block = treeMaker.Block(0, listBuffer.toList());

        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
                jcVariableDecl.getName(), jcVariableDecl.vartype, null);;
        JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(modifiers, getSetterName(jcVariableDecl.getName())
                , null,  List.nil(),List.of(param), List.nil(), block, null);
        return jcMethodDecl;
    }

    private JCTree makeGetterDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> listBuffer = new ListBuffer<>();
        listBuffer.add(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
        JCTree.JCBlock block = treeMaker.Block(0, listBuffer.toList());
        JCTree.JCAnnotation jcAnnotation = treeMaker.Annotation(treeMaker.Select(
                treeMaker.Ident(names.fromString("com.snail.compile.annotation")), names.fromString("Test")), List.nil());
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC, List.of(jcAnnotation));
        JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(modifiers, getNewMethodName(jcVariableDecl.getName())
                , jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), block, null);
        return jcMethodDecl;
    }

    private Name getSetterName(Name name) {
        String s = name.toString();
        return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

    private Name getNewMethodName(Name name) {
        String s = name.toString();
        return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }
}
